home *** CD-ROM | disk | FTP | other *** search
- # include <stdio.h>
- # include <func.h>
- # include <pv.h>
- # include <ingres.h>
- # include <aux.h>
- # include <access.h>
- # include <symbol.h>
- # include <lock.h>
- # include <signal.h>
- # include <errors.h>
-
-
- /*
- ** COPY -- Performs an ingres COPY.
- **
- ** Trace Flags:
- ** 30
- */
-
-
- # define MAXMAP 3 * MAXDOM
- # define DUMMY 'd'
- # define ESCAPE '\\'
-
- extern short tTdbu[100];
- extern int copy();
- extern int null_fn();
- extern char *dumvalue();
-
- struct fn_def CopyFn =
- {
- "COPY",
- copy,
- null_fn,
- null_fn,
- NULL,
- 0,
- tTdbu,
- 100,
- 'Z',
- 0
- };
-
-
-
-
- struct map
- {
- char name[MAXNAME+1]; /* attribute name */
- char ftype; /* attfrmt of file domain */
- char rtype; /* attfrmt of relation domain */
- int flen; /* attfrml of file domain */
- int rlen; /* attfrml of relation domain */
- int roffset; /* attoff of relation domain */
- int used; /* tag for duplicate checking */
- char *fdelim; /* pointer to list of file param delims */
- char *paramname; /* pointer to original parameter name */
- /* used for supplying domain name in case of error */
- };
- struct map Map[MAXMAP]; /* one entry for each user
- specified domain in copy statement. */
-
- int Mapcount; /* number of Map entries */
-
-
- DESC Des; /* descriptor for copied relation */
-
- extern struct out_arg Out_arg; /* user defined formats for numeric output */
-
- FILE *File_iop; /* i/o file pointer */
- char *Filename; /* pointer to file name */
-
- int Into; /* into is one if this is a copy into file */
-
- char Inbuf[BUFSIZ]; /* input holder */
- char Outbuf[BUFSIZ]; /* output holder */
-
- long Tupcount; /* number of tuples processed */
- char *Relname; /* name of relation */
- long Duptuple; /* number of duplicate tuples */
- long Baddoms; /* number of domains with control chars */
- long Truncount; /* number of truncations on a c0 field */
- int Piped[2]; /* pipe descriptor for copy communication */
- char *Cpdomains[] = /* dummy domain names for copy "into" */
- {
- "nl", "\n",
- "tab", "\t",
- "sp", " ",
- "nul", "\0",
- "null", "\0",
- "comma", ",",
- "colon", ":",
- "dash", "-",
- "lparen", "(",
- "rparen", ")",
- 0
- };
-
- char Delimitor[] = ",\n\t"; /* default delims for c0 & d0 */
-
-
-
- copy(pc,pv)
- int pc;
- PARM pv[];
- {
- extern char *Usercode;
- extern int Noupdt;
- register int i, pid;
- register char *cp;
- int stat;
- void copydone();
- int op;
-
- # ifdef xZTR1
- if (tTf(30,1))
- {
- printf("entered copy\n");
- prvect(pc, pv);
- }
- # endif
- Duptuple = 0;
- Truncount = 0;
- Tupcount = 0;
- Baddoms = 0;
- Relname = pv[0].pv_val.pv_str;
- Into = (pv[pc-2].pv_val.pv_str[0] == 'i');
- Filename = pv[pc-1].pv_val.pv_str;
-
- /* relation must exist and not be a system relation */
- /* in addition a copy "from" can't be done if the user */
- /* doesn't own the relation */
- /* and furthermore it can't have an index */
- i = 0; /* assume all is well */
- if (op = openr(&Des, OR_WRITE, Relname))
- {
- if (op == AMOPNVIEW_ERR)
- i = NOCPVIEW;
- else
- {
- if (op < 0)
- syserr("COPY: openr 1 (%.14s) %d", Relname, op);
- else
- /* non-existant relation */
- i = NOEXIST;
- }
- }
- else
- {
- if (Into)
- {
- if ((Des.reldum.relstat & S_PROTALL)
- && (Des.reldum.relstat & S_PROTRET)
- && !bequal(Usercode, Des.reldum.relowner, UCODE_SZ))
- i = RELPROTECT;
- }
- else
- {
- /* extra checking if this is a copy "from" */
-
- /* must be owned by the user */
- if (!bequal(Usercode, Des.reldum.relowner, UCODE_SZ))
- i = NOTOWNER;
- else
- /* must be updateable */
- if ((Des.reldum.relstat & S_NOUPDT) && Noupdt)
- i = NOUPDT;
- else
- /* must not be indexed */
- if (Des.reldum.relindxd > 0)
- i = DESTINDEX;
- }
- }
- if (i)
- {
- closer(&Des);
- return (error(i, Relname, 0)); /* relation doesn't exist for this user */
- }
-
- /* check that file name begins with a "/" */
- cp = Filename;
- while (*cp == ' ')
- cp++;
- if (*cp != '/')
- {
- closer(&Des);
- return (error(FULLPATH, Filename, 0));
- }
-
- /* fill map structures with transfer information */
- if (i = mapfill(&pv[1]))
- {
- closer(&Des);
- return (i); /* error in user semantics */
- }
-
- /* fork a child process which will run as the real user */
- /* that child will complete the copy and exit */
- if (pipe(Piped))
- syserr("copy:can't make pipe");
- if ((pid = fork()) < 0)
- syserr("copy:can't fork");
- if (pid)
- {
- /* the ingres parent */
- close(Piped[1]);
- ruboff(0); /* interrupts off */
- stat = fullwait(pid, "copy");
- if (read(Piped[0], &Des.reladds, 4) != 4)
- syserr("copy:can't read pipe");
- close(Piped[0]);
- closer(&Des); /* close the rel */
- rubon();
- /* if stat is != 0 then add on 5800 for error */
- if (stat)
- stat += 5800;
- return (stat); /* done */
- }
-
- /* the child. change to run as the real user */
- if (signal(SIGINT, SIG_IGN) != SIG_IGN)
- signal(SIGINT, copydone); /* clean up on rubout */
- setuid(getuid());
- setgid(getgid());
- if (Into) /* from relation into file */
- {
- if ((File_iop = fopen(Filename, "w")) == NULL) /* create file for user */
- i = nferror(NOFILECRT, Filename, 0); /* cant create file */
- else
- {
- if (Lockrel) /* set a shared lock on relation*/
- setrll(A_SLP, Des.reltid.ltid, M_SHARE);
- i = rel_file();
- }
- }
- else /* from UNIX file into relation */
- {
- if ((File_iop = fopen(Filename, "r")) == NULL)
- i = nferror(NOFILEOPN, Filename, 0); /* cant open user file */
- else
- {
- if (Lockrel) /* set an exclusive lock on relat*/
- setrll(A_SLP, Des.reltid.ltid, M_EXCL);
- i = file_rel();
- if (Duptuple)
- nferror(DUPTUPS, locv(Duptuple), 0); /* warning only */
- if (Baddoms)
- nferror(BADDOMS, locv(Baddoms), 0); /* warning only */
- }
- }
- copydone(i);
- }
- /*
- ** Finish up and exit after a copy or interrupt
- **
- ** I is the return code. Since only a byte can be
- ** returned, only the least significant 2 decimal
- ** digits are returned. i is either 0 or a number like 58??
- */
-
- copydone(i)
- int i;
- {
- if (Lockrel) /* unlock relation */
- unlrl(Des.reltid.ltid);
- if (Truncount)
- nferror(TRUNCCHARS, locv(Truncount), 0); /* warning only */
- /* force the updates to be flushed */
- cleanrel(&Des);
- if (File_iop)
- fclose(File_iop);
- if (write(Piped[1], &Des.reladds, 4) != 4)
- syserr("copyc:can't writepipe");
- exit (i % 100);
- }
- /*
- ** REL_FILE -- copy from relation to file
- */
-
- rel_file()
- {
- int j;
- struct tup_id tid, limtid;
- char *cp, save;
- register int offset;
- register int i;
- register struct map *mp;
-
- /* set scan limits to scan the entire relation */
- if (find(&Des, NOKEY, &tid, &limtid))
- syserr("find error");
-
- while ((i = get(&Des, &tid, &limtid, Inbuf, 1)) == 0)
- {
- mp = Map;
- offset = 0;
- for (i = 0; i < Mapcount; i++)
- {
- /*
- ** For cases of char to numeric conversion,
- ** there must be a null byte at the end of the
- ** string. The character just past the current
- ** domain is saved an a null byte inserted
- */
-
- cp = &Inbuf[mp->roffset + mp->rlen]; /* compute address */
- save = *cp; /* get the character */
- *cp = '\0'; /* insert a null */
-
- /*
- ** Special case, we want to copy the tid
- */
- if ( mp->roffset == -1 )
- j = transfer(&tid,mp->rtype,mp->rlen,
- mp->ftype,mp->flen,offset);
- else
- j = transfer(&Inbuf[mp->roffset], mp->rtype,
- mp->rlen, mp->ftype, mp->flen, offset);
- if (j)
- {
- /* bad ascii to numeric conversion or field length too small */
- return (nferror(j, mp->paramname, &Inbuf[mp->roffset], locv(Tupcount), Relname, Filename, 0));
- }
- *cp = save; /* restore the saved character */
- offset += mp->flen;
- mp++;
- }
- Tupcount++;
- if (fwrite(Outbuf, 1, offset, File_iop) != offset)
- syserr("copy:cant write to user file %s", Filename);
- }
- if (i < 0)
- syserr("bad get from rel %d", i);
- return (0);
- }
- /*
- ** file_rel is called to transfer tuples from
- ** the input file and append them to the relation
- **
- ** Char domains are initialized to blank and numeric
- ** domains are initialized to zero.
- */
-
- file_rel()
- {
- register int i, j;
- register struct map *mp;
- struct tup_id tid;
-
- clr_tuple(&Des, Outbuf);
-
- /* copy domains until an end of file or an error */
- for (;;)
- {
- mp = Map;
- for (i = 0; i < Mapcount; i++)
- {
- if ((j = bread(mp)) <= 0)
- {
- if (j < 0)
- {
- i = 1; /* force an error */
- j = UNDETC0; /* unterminated string */
- }
- else
- j = UNEXEOF; /* end of file */
- if (i) /* error only if end of file during a tuple or unterminated string */
- {
- i = nferror(j, mp->paramname, locv(Tupcount), Filename, Relname, 0);
- }
- return (i);
- }
- j = transfer(Inbuf, mp->ftype, mp->flen, mp->rtype, mp->rlen, mp->roffset);
- if (j)
- {
- /* bad ascii to numeric or field length too small */
- return (nferror(j, mp->paramname, Inbuf, locv(Tupcount), Filename, Relname, 0));
- }
- mp++;
- }
- Tupcount++;
- if ((j = insert(&Des, &tid, Outbuf, 1)) < 0)
- syserr("insert error %d rel=%s", j, Relname);
- if (j == 1)
- Duptuple++;
- mp++;
- }
- /*
- ** This statement was here -- i don'T think it does anything, but we'll see
- ** return (0);
- */
- }
- /*
- ** transfer copies data from "*in" to
- ** Outbuf doing conversions whenever
- ** necessary
- */
-
- transfer(in, sf, sl, df, dl, doff)
- ANYTYPE *in; /* pointer to input chars */
- char sf; /* source format */
- int sl; /* source length */
- char df; /* destination format */
- int dl; /* destination length */
- int doff; /* destination offset */
- {
- register char *outp;
- register ANYTYPE *inp;
- register int i;
- int j;
- short smalli;
- char temp[MAXFIELD]; /* holds char during conversions to ascii */
- float f;
- double d;
- long l;
-
-
- outp = &Outbuf[doff];
- inp = in;
-
- if (sf == DUMMY)
- /* if source format is a dummy fields then
- nothing else need be done */
- return (0);
-
- if (df == DUMMY)
- {
- /* fill field with dummy domain character */
- i = dl; /* i equals the number of chars */
- while (i--)
- *outp++ = sf; /* sf holds dummy char */
- return (0);
- }
-
- if (sf != CHAR)
- {
- if (df == CHAR) /* numeric to char conversion */
- {
- switch (sl)
- {
- /* int of size 1 or 2 */
- case 1:
- itoa(inp->i1type, temp);
- break;
-
- case 2:
- itoa(inp->i2type, temp); /* convert to ascii */
- break;
-
- /* int or float of size 4 */
- case 4:
- if (sf == INT)
- {
- smove(locv(inp->i4type), temp); /* convert and copy */
- }
-
- else
- {
- ftoa(inp->f4type, temp, dl, Out_arg.f4prec, Out_arg.f4style);
- }
- break;
-
- /* float of size 8 */
- case 8:
- ftoa(inp->f8type, temp, dl, Out_arg.f8prec, Out_arg.f8style);
- break;
-
- /* there is no possible default */
- default:
- syserr("bad domain length %d",sl);
- }
-
- j = length(temp);
- if ((i = dl - j) < 0)
- return (5808); /* field won't fit */
-
- /* blank pad from left. Number will be right justified */
- while (i--)
- *outp++ = ' ';
-
- bmove(temp, outp, j);
- return (0);
- }
-
- if (convert(inp, outp, sf, sl, df, dl)) /* numeric to numeric transfer */
- return (DOMTOOSMALL); /* numeric truncation error */
- return (0);
- }
-
- /* character to numeric conversion */
- /* and character to character conversion */
- switch (df)
- {
-
- case CHAR:
- i = sl;
- if (!i)
- {
- i = length(inp->c0type);
- }
- if (i > dl)
- i = dl;
- if (charmove(inp->c0type, outp, i))
- Baddoms++;
- for (outp += i; i<dl; i++)
- *outp++ = ' ';
- return (0);
-
- case FLOAT:
- if (atof(inp->c0type, &d))
- return (BADINPUT); /* bad conversion to numeric */
- if (dl == 8)
- bmove(&d, outp, dl);
- else
- {
- f = d; /* f8 to f4 conversion */
- bmove(&f, outp, dl);
- }
- return (0);
-
- case INT:
- if (dl == 4)
- {
- if (atol(inp->c0type, &l))
- return (5809);
- bmove(&l, outp, 4);
- return (0);
- }
- smalli = atoi(inp->c0type);
- if (dl == 1) {
- if ((smalli < -128) || (smalli > 127))
- return (5809);
- df = smalli;
- bmove(&df, outp, dl);
- } else
- bmove(&smalli, outp, dl);
- return (0);
- }
- }
- /*
- ** moves a character string from "in"
- ** to "out" removing any control characters.
- ** returns true if any control characters were found
- */
-
- charmove(in, out, length)
- char *in, *out;
- int length;
- {
- register char *ip, *op;
- register int l;
- int bad;
-
- bad = FALSE;
- ip = in;
- op = out;
- l = length;
-
- while (l--)
- if ((*op++ = *ip++) < ' ')
- {
- *(op-1) = ' ';
- bad = TRUE;
- }
- return (bad);
- }
- /*
- ** Mapfill fills the Map structure with the list
- ** of user supplied attributes. It then reads
- ** the list of relation attributes and checks
- ** for matching attribute names.
- **
- ** if an error occures then mapfill returns -1
- ** else it returns 0
- **
- ** Mapfill performs special processing on
- ** dummy domains.
- **
- ** If no user attributes are given, then "given"=FALSE
- ** and each attribute in the relation is set up to be
- ** copied in the formats and order in which they
- ** exist in the relation
- */
-
- mapfill(aptr)
- PARM aptr[];
- {
- register PARM *ap;
- register struct map *mp;
- register int i;
- char *fp;
- extern DESC Attdes;
- struct attribute att;
- struct tup_id tid, limtid;
- int given, cnt;
- char *zcheck();
-
- Mapcount = 0;
- mp = Map;
- ap = aptr;
-
- /* Gather list of user supplied attributes */
-
- while (*(ap->pv_val.pv_str) != '\0')
- {
- /* check for overflow */
- if (Mapcount == MAXMAP)
- return (error(TOOMANYATTR, 0)); /* more than MAXMAP specifiers */
-
- mp->paramname = (ap->pv_val).pv_str; /* save pointer to user supplied name */
- pmove(((ap++)->pv_val).pv_str, mp->name, MAXNAME, ' ');
- fp = ((ap++)->pv_val).pv_str; /* fp points to format string */
- mp->used = 0;
- mp->rlen = 0; /* zero in case this is a dummy domain */
- mp->roffset = 0;
- mp->fdelim = 0;
- /* check domain type in *fp */
- switch (*fp++)
- {
-
- case 'c':
- i = CHAR;
- if ((mp->fdelim = zcheck(fp)) == 0)
- return (-1); /* bad delimitor */
- break;
-
- case 'f':
- i = FLOAT;
- break;
-
- case 'i':
- i = INT;
- break;
-
- case DUMMY:
- i = DUMMY;
- if ((mp->fdelim = zcheck(fp)) == 0)
- return (-1);
- break;
-
- default:
- return (error(BADATTRTYPE, mp->paramname, --fp, 0));
- }
- mp->ftype = i;
-
-
- /* convert format length to binary */
- mp->flen = atoi(fp);
- if (mp->flen < 0 ||
- mp->flen > 511 ||
- (mp->ftype == FLOAT && mp->flen != 4 && mp->flen != 8) ||
- (mp->ftype == INT && mp->flen != 1 && mp->flen != 2 && mp->flen != 4))
- {
- return (error(BADATTRLEN, mp->paramname, --fp, 0)); /* bad length for attribute */
- }
-
- /* process dummy domain if any */
- if (Into && mp->ftype == DUMMY && mp->flen)
- {
- if ((fp = dumvalue(mp->paramname)) == 0)
- return (5807); /* bad dummy name */
- mp->rtype = *fp; /* use first char of string */
- }
-
- /* check for format of type "c0delim" on copy "into" */
- if (Into && mp->flen == 0 && mp->fdelim != Delimitor)
- {
- fp = mp->fdelim;
-
- /* is there room for a dummy domain? */
- mp++;
- if (++Mapcount == MAXMAP)
- return (error(TOOMANYATTR, 0)); /* no room */
-
- /* create a dummy entry */
- mp->ftype = DUMMY;
- mp->flen = 1;
- mp->rtype = *fp;
- mp->roffset = mp->rlen = 0;
- }
-
- mp++;
- Mapcount++;
- }
- /* if no atributes were given, set flag */
- if (Mapcount)
- given = TRUE;
- else
- given = FALSE;
-
- /* open attribute relation and prepare for scan */
- opencatalog("attribute", OR_READ);
-
- setkey(&Attdes, &att, Des.reldum.relid, ATTRELID);
- setkey(&Attdes, &att, Des.reldum.relowner, ATTOWNER);
-
- if (find(&Attdes, EXACTKEY, &tid, &limtid, &att))
- syserr("find error for att-rel");
-
- /* scan Map for each relation attribute */
- while ((i = get(&Attdes, &tid, &limtid, &att, 1)) == 0)
- {
- if (!bequal(&Des, &att, MAXNAME+2))
- continue;
- /* if no user attributes were supplied, fake an entry */
- if (!given)
- {
- Mapcount++;
- mp = &Map[att.attid -1];
- mp->rtype = mp->ftype = att.attfrmt;
- mp->rlen = mp->flen = att.attfrml & I1MASK;
- mp->roffset = att.attoff;
- mp->used = 1;
- mp->paramname = mp->name; /* point to name */
- bmove(att.attname, mp->name, MAXNAME); /* copy name */
- continue;
- }
- mp = Map;
-
- /* check each user domain for match with relation domain */
- for (i = Mapcount; i--; mp++)
- {
- if (mp->ftype == DUMMY)
- continue; /* ignore dummy */
- if (!bequal(mp->name, att.attname, 12))
- continue;
-
- mp->rtype = att.attfrmt;
- mp->rlen = att.attfrml & I1MASK;
- mp->roffset = att.attoff;
- mp->used++;
-
- /* check for special case of C0 in a copy "into" */
- if (Into && (mp->flen == 0) && mp->ftype == CHAR)
- {
- switch (mp->rtype)
- {
- case CHAR:
- mp->flen = mp->rlen;
- break;
-
- case INT:
- switch (mp->rlen)
- {
-
- case 1:
- mp->flen = Out_arg.i1width;
- break;
-
- case 2:
- mp->flen = Out_arg.i2width;
- break;
-
- case 4:
- mp->flen = Out_arg.i4width;
- }
- break;
-
- case FLOAT:
- if (mp->rlen == 4)
- mp->flen = Out_arg.f4width;
- else
- mp->flen = Out_arg.f8width;
- }
- }
- /* if this is a copy "from" then break
- otherwise continue. In a copy "into"
- an attribute might be copied more than once */
- if (!Into)
- break;
- }
- }
- if (i < 0)
- syserr("bad get from att-rel %d", i);
-
- /* check that all user domains have been identified */
- cnt = 0;
- mp = Map;
- for (i = Mapcount; i--; mp++)
- {
- cnt += mp->flen;
- if (mp->ftype == DUMMY)
- continue;
- if (!mp->used)
- {
- if ( Into && bequal(mp->name,"tid ",12) )
- {
- mp->flen = 4;
- mp->rtype = INT;
- mp->rlen = 4;
- mp->roffset = -1;
- mp->used++;
- }
- else
- return (error(ATTRNOEXIST, mp->paramname, Relname, 0)); /* unrecognizable domain name */
- }
- }
- /* check that copy into doesn't exceed buffer size */
- if (Into && cnt > BUFSIZ)
- return (error(FILETOOWIDE, 0)); /* cnt too large */
- return (0);
- }
- /*
- ** BREAD
- */
-
- bread(mp)
- struct map *mp;
- {
- register int count, i;
- register char *inp;
- char *dl;
- int esc; /* escape flag */
-
- count = mp->flen;
- inp = Inbuf;
-
- if (count)
- {
- /* block mode. read characters */
- i = fread(inp, 1, count, File_iop);
-
- /* null terminate */
- *(inp + count) = '\0';
-
- return (i == count); /* true -> normal, false ->eof */
- }
-
- /* string mode read */
- /*
- ** Determine the maximum size the C0 field being read can be.
- ** In the case where it is being copied into a CHAR field, then
- ** the size is that of the char field (+1 for the delimitor).
- ** In the case of a numeric, it is limited only by the size of the
- ** buffer area.
- */
- count = mp->rtype == CHAR ? mp->rlen + 1 : BUFSIZ;
- esc = FALSE;
-
- for (;;)
- {
- if ((i = getc(File_iop)) == EOF)
- return (inp == Inbuf ? 0 : -1); /* -1 -> unexpected EOF, 0 -> normal EOF */
-
- if (count > 0)
- {
- count--;
- *inp++ = i;
- }
- else
- {
- if (count == 0)
- {
- /* determine type of overflow */
- if (mp->rtype == CHAR)
- {
- Truncount++;
- count--; /* read until delim */
- }
- else
- {
- return (-1);
- }
- }
- }
- if (esc)
- {
- esc = FALSE;
- continue;
- }
- if (i == ESCAPE)
- {
- esc = TRUE;
- /*
- ** If esc was stored, back it up.
- */
- if (count >= 0)
- {
- inp--; /* remove escape char */
- count++; /* restore counter */
- }
- }
- else
- {
- for (dl = mp->fdelim; *dl; dl++)
- if (*dl == i)
- {
- *(inp-1) = '\0';
- return (1);
- }
- }
- }
- }
- /*
- ** Look for the existence of a param of the
- ** form "0nl" or "00comma" etc.
- **
- ** Returns the correct delim list or 0
- ** if there was a user error
- **
- ** If successful, a null is inserted at the
- ** rightmost '0' so the subsequent atoi will work.
- */
-
- char *
- zcheck(param)
- char *param;
- {
- register char *np, *ret;
-
- np = param;
- ret = Delimitor; /* assume default delimitors */
-
- if (*np++ == '0')
- {
- /* we have a starting zero. trim the rest */
- while (*np == '0')
- np++;
-
- if (*np > '9' || (*np < '0' && *np >= ' '))
- {
- /* we have a special delim on a 0 width field */
- if (ret = dumvalue(np))
- *(--np) = '\0'; /*
- ** end string before delim
- ** Do not alter delimitor but
- ** instead destroy last '0'.
- */
- }
- }
- return (ret);
- }
- /*
- ** Search list of valid dummy names looking
- ** for 'name'. If 'name' is a single char
- ** then use just that name else it is
- ** an error if the name is not found
- */
-
- char *
- dumvalue(name)
- char *name;
- {
- register char **dp, *np, *ret;
-
- dp = Cpdomains; /* get list of valid dummy names */
- np = name;
- ret = 0;
-
- /* first look for a matching key word */
- while (*dp)
- {
- if (sequal(np, *dp++))
- {
- ret = *dp;
- break;
- }
- dp++;
- }
-
- /* If single char, use that char */
- if (length(np) == 1)
- ret = np; /* use first char of name */
- if (ret == 0)
- error(UNRECDUMMY, np, 0);
-
- return (ret);
- }
-